//
// This file is part of cpp-lib, a library of C++ functions and classes.
// Cpp-lib is copyright (C) 2004 and onwards, The CPL Interest Group.
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or (at
// your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library (see the file COPYING); if not, write to the
// Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
// 02111-1307  USA
//
// $Id$
//


#include <iostream>
#include <fstream>
#include <memory>
#include <exception>
#include <stdexcept>
#include <limits>

#include <cstdlib>
#include <ctime>
#include <cctype>
#include <cerrno>

#include "cpp-lib/util.h"


using namespace cpl::util ;


namespace {

std::ostream* die_output = 0 ;

std::string const die_output_name() { return "CPP_LIB_DIE_OUTPUT" ; }

} // anonymous namespace


void cpl::util::set_die_output( std::ostream* os )
{ die_output = os ; }


void cpl::util::death::die(
  std::string const& msg ,
  std::string name ,
  int const exit_code
) {

  if( os && *os        << msg << std::endl ) { goto suicide ; }
  if(       std::cerr  << msg << std::endl ) { goto suicide ; }
  if(       std::clog  << msg << std::endl ) { goto suicide ; }

  if( name == "" )
  { name = die_output_name() ; }

  {
    // If the following fails, we're really f**cked up :-)
    std::ofstream last_chance( name.c_str() ) ;
    last_chance << msg << std::endl ;
  }

suicide:
  this->exit( exit_code ) ;

}

void cpl::util::die(
  std::string const& msg ,
  std::string name ,
  int const exit_code
)
{ death( die_output ).die( msg , name , exit_code ) ; }


void cpl::util::assertion(
  const bool expr ,
  std::string const& expr_string ,
  std::string const& file ,
  const long line
) {

  if( expr ) return ;

  std::ostringstream os ;
  os << "Assertion failed: "
     << expr_string << " (" << file << ":" << line << ")" ;

  die( os.str() ) ;

}


long const cpl::util::check_long
( double const& x , double const& min , double const& max ) {

  if( x < min || x > max ) {

    std::ostringstream os ;
    os << "should be between " << min << " and " << max ;
    throw std::runtime_error( os.str() ) ;

  }

  if( static_cast< long >( x ) != x )
  { throw std::runtime_error( "should be an integer" ) ; }

  return static_cast< long >( x ) ;

}


void cpl::util::scan_past( std::istream& is , char const* const s ) {

  char c ;

restart:

  char const* p = s ;

  while( *p && is.get( c ) ) {

    if( *p != c ) { goto restart ; }
    ++p ;

  }

}


double const cpl::util::utc()
{ return std::time( 0 ) ; }


bool cpl::util::simple_scheduler::action( double const& t ) {

  if( t_last <= t && t < t_last + dt ) { return false ; }
  t_last = t ; return true ;

}


// The cast is necessary because std::isspace(int) is undefined for
// value other than EOF or unsigned char

void cpl::util::chop( std::string& s ) {

  std::size_t i ;
  for(
    i = s.size() ;
    i > 0 && std::isspace( static_cast< unsigned char >( s[ i - 1 ] ) ) ;
    --i
  )
  {}

  s.resize( i ) ;

}


cpl::util::simple_scheduler::simple_scheduler( double const& dt_ )
: t_last( -std::numeric_limits< double >::max() )
{ reconfigure( dt_ ) ; }

void cpl::util::simple_scheduler::reconfigure( double const& dt_ )
{ always_assert( dt_ >= 0 ) ; dt = dt_ ; }


//
// File stuff.
//

using namespace cpl::util::file ;

std::auto_ptr< std::filebuf > cpl::util::file::open_readbuf(
  std::string const& file                ,
  std::string      & which               ,
  std::vector< std::string > const& path
) {

  std::auto_ptr< std::filebuf > ret( new std::filebuf() ) ;

  std::string tried ;

  for( unsigned long i = 0 ; i < path.size() ; ++i ) {

    std::string const pathname = path[ i ] + "/" + file ;

    ret->open
      ( pathname.c_str() , std::ios_base::in | std::ios_base::binary ) ;

    if( !ret->is_open() )
    { tried += "\n" + pathname + ": " + std::strerror( errno ) ; }
    else { which = pathname ; return ret ; }

  }

  ret->open
    ( file.c_str() , std::ios_base::in | std::ios_base::binary ) ;

  if( !ret->is_open() )
  { tried += "\n" + file + ": " + std::strerror( errno ) ; }
  else { which = file ; return ret ; }

  assert( !ret->is_open() ) ;

  throw std::runtime_error
  ( "couldn't open " + file + " for reading:" + tried ) ;

}


std::auto_ptr< std::istream > cpl::util::file::open_read(
  std::string const& file                ,
  std::string      & which               ,
  std::vector< std::string > const& path
) {

  std::auto_ptr< std::streambuf > buf ;
  buf.reset( open_readbuf( file , which , path ).release() ) ;

  return std::auto_ptr< std::istream >( new owning_istream( buf ) ) ;

}


std::auto_ptr< std::filebuf >
cpl::util::file::open_writebuf( std::string const& file ) {

  std::auto_ptr< std::filebuf > ret( new std::filebuf() ) ;

  ret->open( file.c_str() , std::ios_base::out | std::ios_base::binary ) ;

  if( !ret->is_open() ) {

    throw std::runtime_error(
      "couldn't open " + file + " for writing: " + std::strerror( errno )
    ) ;

  }

  return ret ;

}


std::auto_ptr< std::ostream >
cpl::util::file::open_write( std::string const& file ) {

  std::auto_ptr< std::streambuf > buf ;
  buf.reset( open_writebuf( file ).release() ) ;

  return std::auto_ptr< std::ostream >( new owning_ostream( buf ) ) ;

}


std::string cpl::util::file::basename(
  std::string const& name ,
  std::string const& suffix
) {

  if(
       name.size() >= suffix.size()
    && std::equal( name.end() - suffix.size() , name.end() , suffix.begin() )
  )
  { return std::string( name.begin() , name.end() - suffix.size() ) ; }
  else
  { return name ; }

}
